// The solution is to keep a circular buffer of past character state and input for the local player on the client, 
// then when the client receives a correction from the server, it first discards any buffered state older than the corrected state from the server, 
// and replays the state starting from the corrected state back to the present “predicted” time on the client using player inputs stored in the circular buffer. 
// In effect the client invisibly “rewinds and replays” the last n frames of local player character movement while holding the rest of the world fixed.

#include "src/filesystem/be_filesystem.h"
#include "world_client.h"

ClientWorld::ClientWorld()
{
	cout << ":: connecting to host" << endl;
	m_netClient.connectToServer( settings->getCVarS("host"), settings->getCVar("port") );
	cout << ":: world setup" << endl;
	
	// send version to server
	servermessages.push_back("v 0.02a;");

}

void ClientWorld::process( float timeDelta )
{
	// SEND MESSAGES
		if ( servermessages.size() > 0 )
		{
			string msg;
			for ( unsigned int i=0; i < servermessages.size(); i++ )
				msg.append(servermessages[i]);

			m_netClient.sendMessage(msg);

			servermessages.clear();
		}
	
	// RECIEVE MESSAGES
		m_netClient.checkIncomingMessages(&clientmessages);
		parseClientMessages();

		for( unsigned int i=0; i < players.size(); i++)
			if ( players[i]->isJoined() )
				players[i]->getCar()->updatePosition();
}

void ClientWorld::parseClientMessages()
{
	for ( unsigned int i=0; i < clientmessages.size(); i++ )
	{
		string clientmessage = clientmessages[i];

		string msg = parseH.returnUntillStrip( ";", clientmessage );
// 		cerr << "msg: " << msg << endl;
		while ( msg.size() > 0 )
		{
			string ntype = parseH.returnUntillStrip( " ", msg );

			// version request
// 			if ( ntype == "v" )
// 			{
// 				if ( msg == "1" ) // confirm
// 				{
// 					servermessages.push_back("v 0.02a;");
// 				}
// 			}

			if ( ntype == "pl" )
			{
				msg.append(" "); // adding a space for parsing ease
				
				unsigned int myid(0);

				string Smyid = parseH.returnUntillStrip( " ", msg );
				if ( !Smyid.empty() ) myid = atoi( Smyid.c_str() );
				cerr << "my id" << myid << endl;
				
				string Splayerid = parseH.returnUntillStrip( " ", msg );

				while ( Splayerid.size() > 0 )
				{
					cerr << "new player" << myid << endl;
					unsigned int id = atoi( Splayerid.c_str() );
					boost::shared_ptr<Player> p(new Player( id ));
					cerr << " player id:" << p->getId() << endl;
					string Sjoined = parseH.returnUntillStrip( " ", msg );

					if ( Sjoined == "1" )
					{
						p->join( boost::shared_ptr<Car>(new Car( p->getId(), m_map->startgrid, m_dynamicsWorld.get() ) ) );
						cerr << " player is joined:" << p->isJoined() << endl;
					}
					

					players.push_back(p);

					if ( p->getId() == myid )
					{
						cerr << "registering me as p" << endl;
						me = p;
					}

					Splayerid = parseH.returnUntillStrip( " ", msg );
				}
			}

			// car positions
			else if ( ntype == "cp" )
			{
				msg.append(" "); // adding a space for parsing ease

// 				cerr << msg << endl;
				string Splayerid = parseH.returnUntillStrip( " ", msg );
				while ( Splayerid.size() > 0 )
				{
// 					cerr << endl << "ID: '" << Splayerid << "'" << endl;
					btScalar speed(0);
					unsigned int playerid = atoi( Splayerid.c_str() );
					int foundindex = findClient(playerid);
					if ( foundindex > -1 )
					{
						if ( playerid == me->getId() )
						{
							string speedS = parseH.returnUntillStrip( " ", msg );
							if ( !speedS.empty() )
								speed = atof( speedS.c_str() );
						}
					}

					btVector3 pos(0,0,0);
					string X = parseH.returnUntillStrip( " ", msg );
					if ( !X.empty() )
						pos.setX( atof( X.c_str() ) );
					string Y = parseH.returnUntillStrip( " ", msg );
					if ( !Y.empty() )
						pos.setY( atof( Y.c_str() ) );
					string Z = parseH.returnUntillStrip( " ", msg );
					if ( !Z.empty() )
						pos.setZ( atof( Z.c_str() ) );

					btVector3 rot(0,0,0);
					string Xr = parseH.returnUntillStrip( " ", msg );
					if ( !Xr.empty() )
						rot.setX( atof( Xr.c_str() ) );
					string Yr = parseH.returnUntillStrip( " ", msg );
					if ( !Yr.empty() )
						rot.setY( atof( Yr.c_str() ) );
					string Zr = parseH.returnUntillStrip( " ", msg );
					if ( !Zr.empty() )
						rot.setZ( atof( Zr.c_str() ) );

		
					btScalar wheelrot(0);
					string wheelr = parseH.returnUntillStrip( " ", msg );
					if ( !wheelr.empty() )
						wheelrot =  atof( wheelr.c_str() );

		
					// find player in playerlist
					if ( foundindex > -1 )
					{
						boost::shared_ptr<Player> p = players[foundindex];
						if ( p->isJoined() )
						{
							static_cast<BodypartRigid*>(p->getCar()->body.m_bodyparts[0])->myMotionState->m_graphicsWorldTrans.setIdentity();
							static_cast<BodypartRigid*>(p->getCar()->body.m_bodyparts[0])->myMotionState->m_graphicsWorldTrans.setOrigin( pos );
							static_cast<BodypartRigid*>(p->getCar()->body.m_bodyparts[0])->myMotionState->m_graphicsWorldTrans.getBasis().setEulerZYX( rot.getZ(), rot.getY(), rot.getX() ); // -1.5707f = 90 degrees
							p->getCar()->m_vehicle->setSpeed( speed );
							p->getCar()->m_vehicle->setSteeringValue( wheelrot, 0);
							p->getCar()->m_vehicle->setSteeringValue( wheelrot, 1);
						}
					}
					Splayerid = parseH.returnUntillStrip( " ", msg );
				}
			}

			// force feedback
			else if ( ntype == "ff" )
			{
				// force feedback
				if ( *joystickenabled == 1 )
				{
					float forwardWheelSumSide = 0.f;
					string forwardWheelSumSideS = msg;
					if ( !forwardWheelSumSideS.empty() )
						forwardWheelSumSide = atof( forwardWheelSumSideS.c_str() );
					
// 					if ( forwardWheelSumSide != 0. )
// 					{
// 						cerr << forwardWheelSumSide << endl;
						joystick->applyForce( forwardWheelSumSide ); // lastreltime
// 					}
				}
			}

			// map change
			else if ( ntype == "mc" )
			{
				string lmap = msg;
				if ( lmap.size() > 0 )
				{
					// LOAD MAP
					m_map = Map::LoadMap(lmap.c_str(), m_dynamicsWorld.get());
					m_map->getModel()->upload();
					m_map->getSkyboxmodel()->upload();
					entities.push_back(m_map);
					for ( unsigned int e=0; e < entities.size(); e++ )
						entities[e]->updatePosition();
				}
			}

			// player joins
			else if ( ntype == "jo" )
			{
// 				cerr << "jo called" << endl;
				string Splayerid = msg;
				if ( Splayerid.size() > 0 )
				{
					cerr << Splayerid << " joins" << endl;
					unsigned int playerid = atoi( Splayerid.c_str() );
					stringstream newlog; newlog << "player " << playerid << " joined";
// 					Logbuffer::Instance()->add(newlog);
					newLogMessage(newlog.str());
					int foundindex = findClient(playerid);
					if ( foundindex > -1 )
					{
// 						cerr << "adding new car" << endl;
							players[foundindex]->join ( boost::shared_ptr<Car>(new Car( players[foundindex]->getId(), m_map->startgrid, m_dynamicsWorld.get()) ) );
// 						cerr << "new car added" << endl;
					}
				}
// 				cerr << "jo done" << endl;
			}

			// player unjoins
			else if ( ntype == "uj" )
			{
// 				cerr << "uj called" << endl;
				string Splayerid = msg;
				if ( Splayerid.size() > 0 )
				{
					cerr << Splayerid << " unjoins" << endl;
					unsigned int playerid = atoi( Splayerid.c_str() );
					stringstream newlog; newlog << "player " << playerid << " left";
// 					Logbuffer::Instance()->add(newlog);
					newLogMessage(newlog.str());
					int foundindex = findClient(playerid);
					if ( foundindex > -1 )
					{
						players[foundindex]->unjoin();
					}
				}
// 				cerr << "uj done" << endl;
			}

			// new player
			else if ( ntype == "np" )
			{
				string Splayerid = msg;
				if ( Splayerid.size() > 0 )
				{
					unsigned int playerid = atoi( Splayerid.c_str() );
					int foundindex = findClient(playerid);
					if ( foundindex == -1 )
					{
						cerr << "player " << playerid << " connected" << endl;
						stringstream newlog; newlog << "player " << playerid << " connected";
// 						Logbuffer::Instance()->add(newlog);
						newLogMessage(newlog.str());
						players.push_back(boost::shared_ptr<Player>(new Player( playerid )));
						cerr << "new player added" << endl;
					}

// 					cerr << playerid << endl;
// 					exit(0);
				}
// 				cerr << Splayerid << endl;
// 				exit(0);
			}

			// player disconnects
			else if ( ntype == "pd" )
			{
				string Splayerid = msg;
				if ( Splayerid.size() > 0 )
				{
					unsigned int playerid = atoi( Splayerid.c_str() );
					int foundindex = findClient(playerid);
					if ( foundindex > -1 )
					{
						cerr << "player " << playerid << " disconnected" << endl;
						stringstream newlog; newlog << "player " << playerid << " disconnected";
						newLogMessage(newlog.str());
						players[foundindex]->unjoin();
						players.erase( players.begin()+foundindex );
					}
				}
			}

			// player finished race
			else if ( ntype == "pf" )
			{
				string Splayerid = parseH.returnUntillStrip( " ", msg );
				if ( Splayerid.size() > 0 )
				{
					unsigned int playerid = atoi( Splayerid.c_str() );
					int foundindex = findClient(playerid);
					if ( foundindex > -1 )
					{
						
						string ftimeS = msg;

						cerr << "player " << playerid << " finished: " << ftimeS << endl;
						stringstream newlog; newlog << "player " << playerid << " finished: " << ftimeS;
						newLogMessage(newlog.str());
					}
				}
			}
			
			else if ( ntype == "ms" )
			{
// 				Logbuffer::Instance()->add(msg);
				newLogMessage(msg);
			}

			msg = parseH.returnUntillStrip( ";", clientmessage );
		}
	}
	clientmessages.clear();
}

ClientWorld::~ClientWorld()
{
}
